package interpreter

import (
	"errors"
	"fmt"
	"learning/gooop/behavioral_patterns/interpreter/tokens"
)


type tLexer struct {
	chars []rune
	count int
	pos int
	tokens []*tTokenNode
}

func newLexer(sql string) *tLexer {
	chars := []rune(sql)
	return &tLexer{
		chars: chars,
		count: len(chars),
		pos: 0,
		tokens: make([]*tTokenNode, 0),
	}
}


func (me *tLexer) push(it *tTokenNode) {
	me.tokens = append(me.tokens, it)
}

func (me *tLexer) Parse() (error, []*tTokenNode) {
	fnMatchingWord := func(t tokens.Tokens) bool {
		pattern := string(t)
		from := me.pos
		if me.MatchingConst(pattern) && me.MatchingSpace() {
			me.push(newTokenNode(t, string(t), from, me.pos - 1))
			return true
		}

		return false
	}

	fnMatchingOP := func(t tokens.Tokens) bool {
		pattern := string(t)
		from := me.pos
		if me.MatchingConst(pattern) {
			me.push(newTokenNode(t, string(t), from, me.pos - 1))
			return true
		}

		return false
	}

	for {
		// eof
		if me.IsEof() {
			return nil, me.tokens
		}

		me.SkipSpace()
		from := me.pos

		// select, from, where
		if fnMatchingWord(tokens.Select) || fnMatchingWord(tokens.From) || fnMatchingWord(tokens.Where) {
			continue
		}

		// start,comma
		if fnMatchingWord(tokens.Star) || fnMatchingWord(tokens.Comma) {
			continue
		}

		// and, or
		if fnMatchingWord(tokens.And) {
			continue
		} else if fnMatchingWord(tokens.OR) {
			continue
		}

		// like, not like
		if fnMatchingWord(tokens.Like) {
			continue
		} else if fnMatchingWord(tokens.NotLike) {
			continue
		}

		// (, )
		if fnMatchingOP(tokens.LB) {
			continue
		} else if fnMatchingOP(tokens.RB) {
			continue
		}

		// =,<>,>,>=,<,<=
		if fnMatchingOP(tokens.Equal) {
			continue
		} else if fnMatchingOP(tokens.NotEqual) {
			continue
		} else if fnMatchingOP(tokens.GreaterEqual) {
			continue
		} else if fnMatchingOP(tokens.Greater) {
			continue
		} else if fnMatchingOP(tokens.LessEqual) {
			continue
		} else if fnMatchingOP(tokens.Less) {
			continue
		}

		// identifier
		b,v := me.MatchingIdentifier()
		if b {
			me.push(newTokenNode(tokens.Identifier, v, from, from + len(v)))
			continue
		}

		// string literal
		b,v = me.MatchingString()
		if b {
			me.push(newTokenNode(tokens.StringLiteral, v, from, from + len(v)))
			continue
		}

		// int literal
		b,v = me.MatchingInt()
		if b {
			me.push(newTokenNode(tokens.IntLiteral, v, from, from + len(v)))
			continue
		}

		// unknown
		return errors.New(fmt.Sprintf("unknown token at %v", from)), nil
	}
}


func (me *tLexer) IsEof() bool {
	return me.pos >= me.count
}

func (me *tLexer) SkipSpace() {
	for {
		if me.pos >= me.count {
			break
		}

		if chars.IsSpace(me.Char()) {
			me.pos++
		} else {
			break
		}
	}
}

func (me *tLexer) MatchingConst(s string) bool {
	pattern := []rune(s)
	for i,it := range pattern {
		n := me.pos + i
		if n >= me.count {
			return false
		}
		if me.chars[n] != it {
			return false
		}
	}

	me.pos += len(pattern)
	return true
}

func (me *tLexer) Char() rune {
	if me.pos >= me.count {
		return 0
	}

	return me.chars[me.pos]
}

func (me *tLexer) MatchingSpace() bool {
	if chars.IsSpace(me.Char()) {
		me.pos++
		return true
	}
	return false
}

func (me *tLexer) MatchingString() (bool,string) {
	mark := me.pos

	if me.Char() != '\'' {
		return false, ""
	}

	i := mark
	for {
		i++

		switch me.chars[i] {
		case '\'':
			me.pos = i + 1
			return true, string(me.chars[mark:me.pos])

		default:
			if i >= me.count {
				return false, ""
			}
		}
	}
}


func (me *tLexer) MatchingIdentifier() (bool, string) {
	mark := me.pos

	c := me.Char()
	if !(chars.IsUnderscore(c) || chars.IsLetter(c)) {
		return false, ""
	}

	i := mark
	for {
		i++
		if i > mark + 30 {
			return false,""
		}

		it := me.chars[i]
		if chars.IsLetter(it) || chars.Is09(it) || chars.IsUnderscore(it) {
			continue

		} else {
			me.pos = i
			return true, string(me.chars[mark:i])
		}
	}
}


func (me *tLexer) MatchingInt() (bool, string) {
	if me.Char() == '0' {
		me.pos++
		return true, "0"
	}

	mark := me.pos

	if !chars.Is19(me.Char()) {
		return false, ""
	}

	i := mark
	for {
		i++
		if i >= me.count {
			me.pos = me.count
			return true, string(me.chars[mark:])
		}

		if i > mark + 10 {
			return false, ""
		}

		it := me.chars[i]
		if chars.Is09(it) {
			continue
		}

		if chars.IsSpace(it) {
			me.pos = i - 1
			return true, string(me.chars[mark:i])

		} else {
			return false, ""
		}
	}
}